---
title: DDD 知识点总结
authors: [wkq]
tags: [ddd]
---
领域驱动设计里“领域”这个词的指的是软件要解决的那些业务问题，所以也叫做业务领域。


DDD 是一种开发复杂软件的系统化的方法学和思想，建立在面向对象方法学和敏捷软件开发方法之上，并从面向对象和敏捷中提炼出了一套原则、模式和实践，使面向对象方法学在企业应用中更加容易学习和掌握。

DDD 的核心是领域建模。领域模型是浓缩的领域知识，DDD 非常重视业务与技术人员的沟通，以及如何应对变化。

<!-- truncate -->
## DDD 的基本开发流程

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241207131029.png"/>

DDD 是以领域模型为核心的，开发步骤可以总体上分为模型的建立和模型的实现，模型的建立一定要使用业务语言，模型的实现则增加了技术实现。模型的建立，是通过领域专家和开发人员的协作共同完成的。

捕获行为需求：相当于“需求获取”，这一步需要识别需求里有哪些流程、那些功能、每个功能由那些角色操作，会产生什么结果。这一步可以使用的方法有，用例建模、事件风暴、用户故事地图。

领域建模：相当于建立“分析模型”，通过建立领域模型，把需求里的业务知识描述清楚。


## 事件风暴
为了理解需求，需要分析系统具有哪些功能，这些功能能由那些人操作，会产生什么效果。这个过程在传统上叫做“捕获行为需求”。

捕获行为需求的方法有很多，比如“use Case“。”事件风暴“。

下图是事件风暴的主要过程。
<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241207162120.png"/>

* <RedSpan>识别领域事件：找到业务流程中发生了那些事情。</RedSpan>
* <RedSpan>识别命令： 进一步说明是什么角色，做了什么操作，导致上述事件的发生。</RedSpan>
* <RedSpan>识别领域名词：从领域事件和命令中找到名词性概念，作为下一步领域建模的基础。</RedSpan>


开始事件风暴前，需要做一些准备工作。

首先是人员的准备，事件风暴要求业务人员（领域专家，一般由业务部门的专家或者产品经理等角色来担任）和技术人员（架构师、技术经理及开发人员）共同协作。

第二是场地准备，需要找一个较大的会议室，至少有一面足够长的墙。

最后是准备一些彩色的便利贴，到时候会把事件风暴的内容写在上面。

### 识别领域事件

所谓领域事件，就是在业务过程中，业务人员要关注的那些已经发生的事情。比如，订单已提交、商品已签收等等。表示 <RedSpan>业务流程中每个步骤引发的结果</RedSpan>

:::tip
事件风暴的作者认为，从结果入手来梳理需求，比从操作入手，更容易把业务想清楚。事件风暴中的事件二字就来源于领域事件。
:::
领域事件的命名，一般是完成时+被动语法。

识别领域事件分为两大步：
1. 参加的人，各自写出领域事件；
2. 一起讨论，统一理解，<RedSpan>协作才是事件风暴的精髓。</RedSpan>

在这个过程中，已经开始形成统一语言。

:::tip 统一语言
统一语言是 DDD 中的一个核心模式，指的是业务人员和开发人员使用的语言要一致。语言一致就意味着背后对领域知识的理解一致。
:::

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241207174021.png"/>



* <RedSpan>不要把技术事件当成领域事件</RedSpan>，比如数据库事务已回滚、缓存已命中等，领域事件一定是领域专家关注的业务术语。
* <RedSpan>查询功能不算领域事件</RedSpan>，领域事件应该是对事物产生了影响，并被记录的事情。比如增删改、发送消息。
### 识别命令

<RedSpan>命令（command）就是引发领域事件的操作，可以通过分析领域事件得到。除了识别出命令本身外，还需要识别出谁执行的命令，以及为了执行命令需要查询出什么数据。</RedSpan>

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208005745.png"/>

执行者是某个角色，而不是具体的人。


### 识别领域名词

<RedSpan>领域名词，是从命令、领域事件、执行者、查询是数据里找到的名词性概念。</RedSpan>

例如，对于添加客户这个命令而言，受到影响的名词性概念是 ”客户“ ；类似地，对于客户已添加这个领域事件，是由于”客户“这个名词性概念的状态变化所导致的。


识别领域名词的操作步骤可以分成两小步。

第一步，挪动便利贴，把围绕同一个名词的命令、事件、执行者、查询数据摆在一起，分成好几堆，贴在墙上。


第二步，把领域名词写在大一点的便利贴上，贴在每堆便利贴的中间，如下图：

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208010301.png"/>


### 事件风暴的作用

事件风暴是一种通过协作的方式捕获行为需求的方法，在这个过程里，<RedSpan>事件风暴是业务人员和技术人员一起协作，捕获行为需求、消化领域知识、形成统一语言的一种方法。</RedSpan>

命令是引发领域事件的操作，可以从领域事件反推出来，此外，还可以识别出发出命令的执行者；为了完成命令需要查询出的数据。

领域名词是隐含在命令和领域事件中的名词性概念。这些名词都将作为领域建模的素材。


* 领域事件的作用：从代码实现的角度来看，<RedSpan>领域事件一般会对应一段代码逻辑，这段逻辑可能会最终改变数据库中的数据</RedSpan>。在事件驱动的架构中，一个<RedSpan>领域事件可能会表现为一个向外部发送的异步消息</RedSpan>。
* 命令的作用：<RedSpan>领域建模时，可以通过对命令的走查（walkthrough），细化和验证领域模型</RedSpan>。在实现层面，一个命令可能对应前端的一个操作，例如按下按钮；对于后端而言，一个命令可能对应一个 API。
* 执行者：<RedSpan>领域建模时，执行者可能本身就是一个领域对象，也可能是领域对象充当的角色，或者是权限管理中的一个角色</RedSpan>。
* 查询数据：<RedSpan>查询数据不产生领域事件，因此也不会有相应的命令</RedSpan>。为了保证功能不遗漏，需要单独分析查询功能。
* 领域名词： 识别领域名词的最终目的是<RedSpan>找到领域模型中的对象</RedSpan>。这一步识别出的名词，虽然很可能就是领域对象，但也有不是的。事件风暴并不是领域建模的合适工具，这些名词将成为领域建模的“素材”，需要领域建模时再详细分析。



### 常见问题
#### 是否要列出所有的领域事件和命令？

列出所有的领域事件和命令没有问题，但是会让结果变得繁琐，抓不住重点。

所以，<RedSpan>在事件风暴中只列出主要的、足以用于表达和交流领域知识的步骤</RedSpan>，例如签订合同、合同生效等等。而像修改合同和删除合同这样的步骤是显而易见的，讨论过程中可以提一下，但是不用列出来，这样是为了保持简洁。

不列出来，为了保证功能不遗漏，可以结合用户故事或者传统的功能列表等方法保存系统功能的全集。
#### 领域事件需要体现严格的时间顺序吗？

只需要按照大致的顺序，贴出领域时间就可以了，这是因为如果要体现严格的顺序，需要用到更复杂的符号，比如条件判断。这会让事件风暴变得非常繁琐。

因此，应该关注点分离。如果要体现严格的时间顺序，可以用流程图、顺序图等方法。时间风暴不必关注这一点。

#### 每个步骤的粒度大小应该多大合适？

这里的步骤，是指一对领域事件和命令。

原则上宜粗不宜细，可以先采用较大的颗粒度，后面必要的时候，再拆分。

#### 事件风暴适合所有项目吗？


事件风暴主要应用在需求不清晰，或者理解不统一的情况下，通过协作的方式理清业务、达成一致，所以通常对于新项目比较适用。



即便在适用的场合，事件风暴也不是唯一的方法，我们还可以用用例分析、用户故事等方法实现类似的目的。只要抓住协作、统一语言等等要点，这些方法都可以用在 DDD 的项目里。




所以，如果你的项目里还没有正规的、令人满意的捕获行为需求方法，那就可以使用事件风暴。如果已经有了成熟的方法，你可以借鉴事件风暴的思路，在原来的方法里加入协作、统一语言、识别领域名词的实践，也能达到同样的效果。

#### 怎么保存和维护事件风暴的结果？
除了绘图之外，事件风暴也可以用表格的形式来保存

| 业务流程 | 命令     | 领域事件    | 执行者  | 查询数据 | 领域对象 |
|------|--------|---------|------|------|------|
| 租户管理 | 添加租户   | 租户已添加   | 管理者  |      |      |
| 组织管理 | 添加开发中心 | 开发中心已添加 | 人事人员 | 企业   |      |
| 组织管理 | 添加开发组  | 开发组已添加  | 人事人员 | 开发中心 |      |
| 组织管理 | 添加直属部门 | 直属部门已添加 | 人事人员 | 企业   |      |
| 员工管理 | 添加员工   | 员工已添加   | 人事人员 | 所属部门 |      |
| 员工管理 | 转移员工   | 员工已转移   | 人事远远 | 目标部门 |      |

#### 怎么保存领域规则

领域规则是重要的领域知识，必须妥善保存。所以，要编写一张 <RedSpan>领域规则表</RedSpan>，把所有的领域规则都汇总在里面，然后把领域规则表和领域模型放在一起，<RedSpan>作为领域知识的重要组成部分</RedSpan>。


<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208015323.png"/>

## 领域建模


领域建模主要有两个目的：
* 将知识可视化，准确、深刻地反映领域知识，并且在业务和技术人员之间达成一致。
* 指导系统的编码与设计，领域模型要比较容易地转化成数据库模式和代码实现。

建立领域模型，主要是为了识别<RedSpan>领域对象，领域对象之间的关系，以及领域对象的关键属性</RedSpan>，必要的时候还需要<RedSpan>将领域对象组织成模块</RedSpan>。

:::tip
系统中要处理的各种“事物”就是领域对象，比如项目、员工等等。
:::

<RedSpan>领域建模使用领域模型图来表达的，通常用 UML 来画。</RedSpan>UML 是统一建模语言，是面向对象建模的国际标准。其中，领域对象类图表示，如下图：

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208115251.png"/>

在领域建模时，说起领域对象时，有时指类，有时指实例，一般要通过上下文来区分。

此外， DDD 中将领域对象又分为实体（entity）和值对象（value object）。


由类和它们之间的关系组成的图叫做类图，这也是领域建模里用到的最主要的图。

### 识别实体
从事件风暴识别出的领域名词出发，开始进行领域建模。首先假定每个领域名词都是一个实体。然后识别实体之间的关联。关联可以分为三种：一对一、一对多和多对多。而这些不同的关联，可以用多重性来表达。
<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208120312.png"/>

这就得到一张最简单的类图了，在领域建模阶段，主要关注的是 <RedSpan>实体和它们之间的关系</RedSpan>，如果实体的名字已经能清晰说明实体的含义，就不需要加属性了。
### 多重性
关联关系（association）在 UML 中称为多重性，关联关系会在类的两边标明它的多重度。
* 1： 表示一个集合中的一个对象对应另一个集合中的 1 个对象。
* 0..*: 表示一个集合中的一个对象对应另一个集合中的 0 个或多个对象。
* 1..*： 表示一个集合中的一个对象对应另一个集合中的一个或多个对象。
* *：表示一个集合中的对象对应另一个集合中的多个对象。

假设有 A 和 B 两类实体，你可以通过问四个问题，来把多重性搞清楚：

* 一个实体 A 最多可以对应多少个实体 B？
* 一个实体 A 最少可以对应多少个实体 B？
* 一个实体 B 最多可以对应多少个实体 A？
* 一个实体 B 最少可以对应多少个实体 A？

还有一种关联是实体自身上的“自关联“，可以表达由某种实体组成的树状或网状结构。

### 识别实体之间的联系
<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208150022.png"/>

一个租户对应一个企业。一个企业也只能作为一个租户。<RedSpan>租户和企业具有一对一的关系</RedSpan>。

在有些情况下，一对一的两个实体是可以合并为一个的，这取决于对这两个概念的关注点是否相同。

因为企业是组织结构中一个概念，租户是 SaaS 应用的概念，即便不基于 SaaS ，企业这个概念也应该存在。所以这是两个不同关注点的概念，不应该合并。

企业、开发中心、直属部门等都是组织类别的实例。<RedSpan>一个组织类别下可以有多个组织，而一个组织只能属于一个组织类别</RedSpan>。

一个组织可以有多个组织作为自己的下级；而一个组织只能有一个组织作为自己的上级。这样就表现出上下层层级的关系。在这个自关联的两端，有下级和上级两个词，在 UML 中称为角色（role）。

在 UML 中，用大括号括起来的内容称为约束（constraint）。和一般性的注释不同，<RedSpan>凡是约束，必须在程序中的某个地方进行实现。</RedSpan>

一个员工可以担任多个岗位，一个岗位也可以有多个员工担任，员工和岗位之间是多对多关联。

一个员工可以充当多个客户的客户经理，而一个客户有且有仅有一个员工作为他的客户经理。

员工和项目之间是多对多关联。

项目经理和项目之间是一对多关联

工时记录可以看作是员工和项目之间的一种多对多关系。


两个实体之间，如果有多个关联。不同关联代表不同的含义，可以用角色名来区分。


<RedSpan>任何多对多关联，通过引入一个表示关联的实体，拆分成两个一对多的关联。</RedSpan>



### 划分模块
人的认知能力是有限的，面对复杂的对象网络，就产生了认知过载。

解决这一问题的方法就是”模块化“，把模型中的业务概念组织成若干高内聚的模块，而模块之间尽量低耦合。

在 UML 中，用包来表示模块（module）。

包的内部可以包含实体，也可以包含另外的包。在 UML 中，任何需要对元素进行分组的场合，都可以使用包。

通过讨论，把模型划分为了四个模块，分别是租户管理、组织管理、项目管理和工时管理。如下图

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208145949.png"/>



划分了模块，就可以从宏观层面理解模型了。宏观层面只关心模型中有那些模块，以及模块间的依赖关系，不关心模块内部的细节。

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241208140535.png"/>

:::tip
带箭头的虚线，在 UML 中表示依赖（dependency）关系，箭头由依赖方指向被依赖方。
:::

<RedSpan>关联关系表示的是数据上的导航关系，依赖关系的意思更为广泛。如果有 A、B 两个元素，有了 A 才能有  B ，那么就可以说 B 依赖于 A。</RedSpan>

### 完善业务规则
### 建立词汇表